﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using NLog;
using Raytracer.Configurations;
using TomanuExtensions;
using RaytracerLib;
using System.Diagnostics;
using System.Drawing;
using RaytracerLib.Renderers;
using TomanuExtensions.Utils;

namespace Raytracer.Generator
{
    public partial class GeneratorForm : Form
    {
        private bool m_stopping = false;
        private bool m_generating = false;
        private static Logger Logger = LogManager.GetLogger("Generator");

        public GeneratorForm()
        {
            InitializeComponent();
        }

        private void event_Load(object sender, EventArgs e)
        {
            numbersTextBox.Text = Configuration.Instance.TestGeneratorNumbers;

            FormsUtils.CenterControlInPanel(centerPanel);

            UpdateButtons(CanGenerate(), true);
        }

        private void UpdateButtons(bool a_enable_buttons, bool a_enable_check)
        {
            generateAllButton.Enabled = a_enable_buttons;
            generateAllXmlsButton.Enabled = a_enable_buttons;
            generateErrorsXmlsButton.Enabled = a_enable_buttons;
            generateSelectedButton.Enabled = a_enable_buttons;
            generateSelectedXmlsButton.Enabled = a_enable_buttons;
            confirmAllErrorsButton.Enabled = a_enable_buttons;

            copyImageForBigCheckBox.Enabled = a_enable_buttons && skipBigCheckBox.Enabled;

            #if !DEBUG
            allowGensInDebugCheckBox.Enabled = !(System.Environment.Is64BitProcess && !Debugger.IsAttached);
            #else
            allowGensInDebugCheckBox.Enabled = a_enable_check;
            #endif
        }

        private void event_generateAllButton_Click(object sender, EventArgs e)
        {
            Generate(false, true);
        }

        private void event_pictureBox_Paint(object sender, PaintEventArgs e)
        {
            centerPanel.PerformLayout();
        }

        private void event_generateAllXmlsButton_Click(object sender, EventArgs e)
        {
            Generate(false, false);
        }

        private void event_numbersTextBox_TextChanged(object sender, EventArgs e)
        {
            Configuration.Instance.TestGeneratorNumbers = numbersTextBox.Text;
        }

        private void event_generateSelectedButton_Click(object sender, EventArgs e)
        {
            Generate(true, true);
        }

        public static List<int> GetFilter(string a_string)
        {
            var nums = a_string.Split(new char[] { ',', ' ' }).Select(
                    s => s.Trim()).Except(s => string.IsNullOrWhiteSpace(s)).
                    Where(s => s.IndexOf('-') == -1);

            var ranges = a_string.Split(new char[] { ',', ' ' }).Select(
                s => s.Trim()).Except(s => string.IsNullOrWhiteSpace(s)).
                Where(s => s.IndexOf('-') != -1);

            List<int> filter = new List<int>();

            filter = nums.Select(s => s.ToInt()).OrderBy(n => n).ToList();

            foreach (var range in ranges)
            {
                var r = range.Split(new char[] { '-' });
                int min = r[0].ToInt();
                int max = r[1].ToInt();
                if (min > max)
                    throw new InvalidDataException("bad range");
                filter.AddRange(Enumerable.Range(min, max - min + 1));
            }

            filter.Sort();

            return filter;
        }

        private bool CanGenerate()
        {
            #if DEBUG
            return allowGensInDebugCheckBox.Checked;
            #else
            return System.Environment.Is64BitProcess && (!Debugger.IsAttached || allowGensInDebugCheckBox.Checked);
            #endif
        }

        private void Generate(bool a_filter, bool a_generate_also_images)
        {
            try
            {
                IEnumerable<TestScene> test_scenes;
                if (a_filter)
                {
                    var filter = GetFilter(numbersTextBox.Text);
                    test_scenes = from ts in TestScenes.List
                                  where filter.Contains(ts.Index)
                                  select ts;
                }
                else
                    test_scenes = TestScenes.List;

                GenerateScenes(test_scenes, a_generate_also_images);
            }
            catch (Exception ex)
            {
                Logger.Error(ex);
                Close();
            }
        }

        private void GenerateScenes(IEnumerable<TestScene> a_test_scenes, bool a_generate_also_images)
        {
            UpdateButtons(false, false);

            m_generating = true;

            Task.Factory.StartNew(() =>
            {
                try
                {
                    int counter = 0;

                    bool canceled = false;

                    foreach (var ts in a_test_scenes)
                    {
                        if (m_stopping)
                        {
                            canceled = true;
                            break;
                        }

                        counter++;

                        Logger.Info("Generating test scene - {0}/{1} - {2} - {3}",
                            counter, a_test_scenes.Count(), ts.Index, ts.Name);

                        GenerateSceneSerie(ts, a_generate_also_images);
                    }

                    if (canceled)
                        Logger.Error("Generating was cancelled");
                    else
                        Logger.Error("All tests generated");
                }
                catch (Exception ex)
                {
                    Logger.Error(ex);
                }
                finally
                {
                    m_generating = false;
                    Invoke(new Action(() => Close()));
                }
            });
        }

        private void event_GeneratorForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (m_generating)
            {
                m_stopping = true;
                e.Cancel = true;
            }
        }

        private void event_generateSelectedXmlsButton_Click(object sender, EventArgs e)
        {
            Generate(true, false);
        }

        private void event_generateErrorsXmlsButton_Click(object sender, EventArgs e)
        {
            var filter = from file in PathResolver.TestScenesDir.GetFiles("*_area_diff.png", SearchOption.AllDirectories)
                       select file.Name.Split(" - ").First().ToInt();

            var test_scenes = from ts in TestScenes.List
                          where filter.Contains(ts.Index)
                          select ts;

            GenerateScenes(test_scenes, true);
        }

        private void GenerateSceneSerie(TestScene a_ts, bool a_generate_also_image)
        {
            GenerateScene(a_ts, null, a_generate_also_image, false);

            if (!skipBigCheckBox.Checked)
            {
                GenerateScene(a_ts, scene => scene.ScaleAbsolute(1.43565467453876788e6),
                    a_generate_also_image, true);
            }
        }

        private void GenerateScene(TestScene a_ts, Action<Scene> a_modifier, bool a_generate_also_image, bool a_big)
        {
            if (m_stopping)
                return;

            Scene scene = a_ts.CreateScene();
            if (a_modifier != null)
                a_modifier(scene);

            Repeatable(scene);

            scene.Name = String.Format("{0} - {1}", a_ts.Index, a_ts.Name);
            if (a_big)
                scene.Name += " - big";

            Logger.Info("    {0}", scene.Name);

            string dir_name = String.Format("{0}\\{1}",
                    PathResolver.TestScenesDir, a_ts.Path);
            string xml_name = String.Format("{0}\\{1}.xml",
                    dir_name,  scene.Name);
            new DirectoryInfo(dir_name).Create();

            scene.SaveToFile(xml_name);

            if (a_generate_also_image)
            {
                if (a_big && copyImageForBigCheckBox.Checked)
                {
                    File.Copy(
                        String.Format("{0}\\{1} - {2}.png", dir_name, a_ts.Index, a_ts.Name),
                        String.Format("{0}\\{1}.png", dir_name, scene.Name), true);

                    File.Delete(String.Format("{0}\\{1}_diff.png", dir_name, scene.Name));
                    File.Delete(String.Format("{0}\\{1}_now.png", dir_name, scene.Name));
                    File.Delete(String.Format("{0}\\{1}_org.png", dir_name, scene.Name));
                }
                else
                    GenerateSceneImage(scene, dir_name);
            }
        }

        public static void Repeatable(Scene a_scene)
        {
            a_scene.RenderOptions.RandomSeed = 34567;
            if (a_scene.ActiveCamera.Film.Sampler.SamplerType == RaytracerLib.Samplers.SamplerType.Jitter)
                a_scene.RenderOptions.Parallel = false;
            if (a_scene.ActiveCamera.Film.Sampler is RaytracerLib.Samplers.LowDiscrepancySequenceSampler)
                a_scene.RenderOptions.Parallel = false;
        }

        private void GenerateSceneImage(Scene a_scene, string a_dir_name)
        {
            Renderer renderer = a_scene.CreateRenderer();

            pictureBox.Invoke(new Action(() =>
            {
                pictureBox.Image = new Bitmap(a_scene.ActiveCamera.Film.GetFilmRect().Width,
                    a_scene.ActiveCamera.Film.GetFilmRect().Height);
                pictureBox.Size = new Size(pictureBox.Image.Width, pictureBox.Image.Height);
            }));

            renderer.ShouldStop += () => m_stopping;

            renderer.UpdateCallback += () =>
            {
                pictureBox.Invoke(new Action(() =>
                {
                    renderer.UpdateBitmap(pictureBox.Image as Bitmap);
                    pictureBox.Refresh();
                }));
            };

            renderer.Render();

            string image_path = String.Format("{0}\\{1}.png",
                a_dir_name, a_scene.Name);

            File.Delete(String.Format("{0}\\{1}_diff.png", a_dir_name, a_scene.Name));
            File.Delete(String.Format("{0}\\{1}_now.png", a_dir_name, a_scene.Name));
            File.Delete(String.Format("{0}\\{1}_org.png", a_dir_name, a_scene.Name));

            if (!m_stopping)
            {
                pictureBox.Invoke(new Action(() =>
                {
                    pictureBox.Image.Save(image_path);
                }));
            }
        }

        private void allowInDebugCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            UpdateButtons(CanGenerate(), true);
        }

        private void event_ConfirmAllErrorsButton_Click(object sender, EventArgs e)
        {
            if (MessageBox.Show("Confirm all errors ?", "Raytracer", MessageBoxButtons.OKCancel) == 
                System.Windows.Forms.DialogResult.OK)
            {
                Logger.Error("Confirming all errors.");
                UpdateButtons(false, false);

                try
                {

                    foreach (var file in PathResolver.TestScenesDir.GetFiles("*_now.png", SearchOption.AllDirectories))
                    {
                        Logger.Info(file.FullName);

                        File.Delete(file.FullName.Replace("_now", ""));
                        File.Delete(file.FullName.Replace("_now", "_diff"));
                        File.Delete(file.FullName.Replace("_now", "_org"));
                        file.Rename(file.FullName.Replace("_now", ""));
                    }
                }
                catch (Exception ex)
                {
                    Logger.Error(ex);
                }
                finally
                {

                    Logger.Error("Confirmed all errors.");
                    Close();
                }
            }
        }

        private void event_skipBigCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            UpdateButtons(CanGenerate(), true);
        }
    }
}